class User {
	// constructor 函数中的 this 指向实例对象自身
	constructor(name) {
		this.name = name
	}

	// 声明的方法存在于实例对象的原型中
	sayHi() {
		console.log(this.name)
	}
}

let user = new User('李耳') // new User 时，constructor 函数会自动调用。

console.log(user) // User { name: '李耳' }
console.log(user.name) // 李耳

user.sayHi() // 李耳

// ****************************************************************************************************************************

// * 在 JS 中，类是一种函数。

class User2 {}
console.log(typeof User2) // function

// * class User2 实际上做了如下的事：
// * 1. 创建一个名为 User2 的函数，该函数成为类声明的结果。该函数的代码体来自于 constructor 方法（如果我们没有编写这个方法，那么它会被假定为空）
// * 2. 存储类中的方法，例如 User2.prototype 中的 sayHi 方法。

// ****************************************************************************************************************************

// * 类的实际解释代码：
class User3 {
	constructor(name) {
		this.name = name
	}

	sayHi() {
		console.log(this.name)
	}
}

// class 是一个函数
console.log(typeof User3) // function

// 函数默认都有 prototype 属性，属性中的 constructor 指向函数自身
console.log(User3 === User3.prototype.constructor) // true

// 类中的方法存在于 prototype 中
console.log(User3.prototype.sayHi) // [Function: sayHi]

// getOwnPropertyNames 返回类中可迭代和不可迭代的所有属性
console.log(Object.getOwnPropertyNames(User.prototype)) // [ 'constructor', 'sayHi' ]

// ****************************************************************************************************************************

// * 类的一些特性：

// * 1. 类虽然是函数，但是它只能通过 new 进行调用，不能当成普通函数进行调用（会报错）。
// * 2. 类中定义的方法不可枚举。类定义将 "prototype" 中的所有方法的 enumerable 标志设置为 true。
// * 3. 类中是使用 "user strict"。在类构造中的所有代码都将自动进入严格模式。

// ****************************************************************************************************************************

// * 类表达式（类似于函数表达式）
let C = class {
	sayHi() {
		console.log('hi~')
	}
}

// * 命名类表达式（规范中没有这样的术语，但是它和命名函数表达式类似，类名只能在内部才能获取到，外部获取不到）
let C2 = class User4 {
	sayHi() {
		console.log(User4)
	}
}

new C2().sayHi() // [class User4]
// console.log(User4) // 报错（外部获取不到）

// * 动态“按需”创建类
function makeClass(phrase) {
	return class {
		sayHi() {
			console.log(phrase)
		}
	}
}

let C3 = makeClass('Hello')
new C3().sayHi() // Hello

// ****************************************************************************************************************************

// * Getters/setters
class C4 {
	constructor(name) {
		// this === {} 执行 constructor 事，this 就是一个空对象了，并且原型指向 C4.prototype

		// 会调用 setters（因为 name 是属于 prototype 中的访问器属性，它存在的，所以 set 会被调用）
		this.name = name
	}

	get name() {
		return this._name
	}

	set name(value) {
		if (value.length < 4) {
			return console.log('名字太短')
		}

		this._name = value
	}
}

let c4 = new C4('大笨蛋')
console.log(c4) // C4 { _name: '大笨蛋' }
console.log(c4.name) // 大笨蛋（name 在原型对象上）

let cc4 = new C4('') // 名字太短

// *** 从技术上来讲，上面这样的类声明可以通过在 User.prototype 中创建 getters 和 setters 来实现。
// 示例代码：效果和在类中什么的 get/set 相同
C4.prototype = {
	constructor: C4,

	set name(value) {
		this._name = value
	},

	get name() {
		return this._name
	},
}

// ****************************************************************************************************************************

// * 计算属性名称 [...]（动态设置方法名）
class C5 {
	['say' + 'Hi']() {
		console.log('sayHi')
	}
}

new C5().sayHi() // sayHi

// ****************************************************************************************************************************

// * Class 字段：类字段，是一种允许添加任何属性的语法（类字段的重要区别是它会挂载到类的实例对象上，而非 C6.prototype 上）。

class C6 {
	name = '铁锤'
	fn = function () {
		console.log(this.name)
	}

	sayHi() {
		console.log(`Hello, ${this.name}`)
	}
}

let c6 = new C6()

console.log(c6) // C6 { name: '铁锤', fn: [Function: fn] }
C6.prototype.sayHi() // Hello, undefined
c6.sayHi() // Hello, 铁锤

// ****************************************************************************************************************************

// * 箭头函数的 this 在其函数定义时，它就被指定了指向它定义时所在的上下文；而 函数声明 和 函数表达式 中的 this 则是在函数运行时才被指定的。

// 例子1：
let arrwFn = () => {
	// * 在定义箭头函数时，这个 this 就被指定了，它指向当前的词法环境（箭头函数自身没有 this 所以它向外部寻找，外部现在的 this 就是全局对象，所以这个 this 就已经被确定指向了全局对象）。
	console.log(this.name)
}

let obj = {
	name: 'obj',
	func() {
		let f = arrwFn // 这里赋值的是一个定义好的箭头函数，定义好的箭头函数，在它定义时，它里面的 this 就已经被指定为它定义位置的上下文了。
		f()
	},
}

obj.func() // undefined

// ****************************

// 例子2（复制到浏览器去运行）：
window.name = 'outer'

let a = () => {
	console.log(this.name)
}

let o1 = {
	name: 'O1',
	a,
}

o1.a() // outer
